package xyz.app.accessors;

import lombok.NonNull;
import xyz.app.Wrapper;
import xyz.app.dependencies.HasDomainMembers;
import xyz.app.dependencies.MemberAccessor;
import xyz.app.AbstractDomain;
import xyz.app.AbstractDomain_;
import xyz.app.dependencies.HasOwners;
import xyz.app.queries.CriteriaWrapperExecute;

import java.util.Objects;

public interface DbDeleter<T extends AbstractDomain> extends DbAccessor<T> {

    default boolean delete(@NonNull Wrapper dto) {

        info("Dao chosen for deletion of domain [{}]", getClass().getSimpleName());

        if (!Objects.equals(dto.domainClass(), domainClass())) return false;

        var success = dto.unwrap(domainClass())
                .filter(this::isNonNew)
                .filter(this::removeOwnersIfAny)
                .map(AbstractDomain::getId)
                .map(this::delete)
                .orElse(false);

        info(success ? "SUCCESS" : "FAILURE" + ". Removal of domain from database [{}]", dto);

        if (success) removeOrphans(dto);
        return success;
    }

    default <V> boolean delete(@NonNull String name, V value) {

        info("""
                ATTEMPT. Remove from database
                Domain type [{}]
                Property (member) name [{}]
                Property (member) value [{}]""",
                domainClass().getSimpleName(), name, value);

        return new CriteriaWrapperExecute.Delete<>(this)
                .whereEqualsOrNull(name, value)
                .executeUpdate();
    }

    private boolean delete(@NonNull Long id) {

        info("ATTEMPT. Delete by ID [{}]", id);
        return delete(AbstractDomain_.ID, id);
    }

    private boolean removeOwnersIfAny(AbstractDomain domain) {

        info("ATTEMPT. Handling owners if any [{}]", domain.string());

        if (!(this instanceof HasOwners<?> hasOwners)) {
            info("Domain by nature is supposed not to have any owners for elimination. Domain might be removed right away");
            return true;
        }

        info("Domain potentially has owners. Must check before domain's own removal");

        if (this instanceof HasOwners.HasRemovableOwners<?>)
            info("Domain potentially has REMOVABLE owners. Must remove them before domain's own removal");

        return hasOwners.removeOwners(domain);
    }

    private boolean isNonNew(@NonNull AbstractDomain domain) {

        var isNonNew = !domain.isNew();

        info(isNonNew
                ? "Domain [{}] has an ID and thus can be removed from database"
                : "Domain [{}] does not have an ID and thus cannot be removed from database",
                domain.string());

        return isNonNew;
    }

    private void removeOrphans(@NonNull Wrapper dto) {

        info("Attempt to remove orphans from database. Owner [{}]", dto);

        if (!(this instanceof HasDomainMembers<?> hasMembers)) return;

        info("Wrapper has members that potentially might have to be removed from database (orphans) [{}]", dto);

        hasMembers.memberAccessors().stream()
                .map(MemberAccessor::getGetter)
                .map(getter -> getter.apply(dto))
                .filter(Objects::nonNull)
                .peek(member -> info("Member found [{}]", member.unwrap().string()))
                .forEach(memberDto -> daoRegistry().getDao(memberDto.domainClass())
                        .filter(dao -> dao instanceof HasOwners.IsRemovableOrphan<?>)
                        .ifPresent(dao -> {
                            info("Potential orphan revealed. Attempt to remove from database [{}]", memberDto);
                            dao.delete(memberDto);
                        }));
    }
}